/* Janus Chat - Version 1.0
 * Authors: Matthew Perry
 *          Michael Freid
 *          Marc Latou
 *          Richard Lee
 *
 * The Janus Chat Client is a highly secured
 * peer-to-peer chat client designed to facilitate
 * the transfer of sensitive data or as a communication
 * medium for the extra paranoid
 *
 * This file contains the header for the Chat class
 * which will encapsulate alot of the data for each
 * individual chat session */

#include "chat.h"
#include "exceptions.h"
#include <QMessageBox>

/* The main constructor:
 * This constructor takes in a socket and connects
 * its signals to the corresponding slots */
Chat::Chat( User* user, bool host, QTcpSocket* sock )
{
    //Set the current state
    myState = NoKeys;

    // Initialize member variables
    me = user;
    peer.first = "Unknown";
    mySock = sock;
    connected = true;
    amiHost = host;

    //Connect the socket signals to their slots
    //Connect mysock's readyRead to this's readSome
    connect( mySock, SIGNAL(readyRead()), this, SLOT(readSome()) );
    //Connect mysock's error() to this's socketError()
    connect( mySock, SIGNAL(error(QAbstractSocket::SocketError)),
             this, SLOT(socketError(QAbstractSocket::SocketError)) );
    //Connect publicKeyRecv to publicKeyStep
    connect( this, SIGNAL(publicKeyRecv(string)), this, SLOT(publicKeyStep(string)) );
    //Connect sessionKeyRecv to sessionKeyStep
    connect( this, SIGNAL(sessionKeyRecv(string)), this, SLOT(sessionKeyStep(string)) );
}

/* The startKeyExchange function:
 * this function begins the process
 * for exchanging keys between two
 * clients */
void Chat::startKeyExchange()
{
    //Send the user's public key
    string sKey = jRsa::serializePublicKey( me->getPublicKey() );
    Message exchange( Message::PublicKeyExchange, sKey );
    writeMsg( exchange );
}

/* The writeMsg function:
 * This function takes a message and
 * writes it to the socket, catching
 * any errors in the process */
void Chat::writeMsg( Message& msg )
{
    int retval = 0;
    switch( msg.getType() ) {
        case Message::DecryptStandard:
            //Only send the encrypted message if the key exchange is done
            if( myState == Complete )
            {
                //Encrypt message and write to socket
                retval = mySock->write( msg.aesEncrypt( mySessionKey ),
                                        Message::MAX_SIZE );
                break;
            }
        case Message::CryptStandard:
        case Message::PublicKeyExchange:
        case Message::SessionKeyExchange:
            //Write the message to the socket
            retval = mySock->write( msg.toChar(), Message::MAX_SIZE );
            break;
        default:
            break;
    }
    //If there was an error
    if( retval < Message::MAX_SIZE )
    {
        //Pop up message box with the errors
        QMessageBox msgBox;
        msgBox.setWindowTitle( "Critical Error" );
        msgBox.setText( "Cannot write to this socket:" );
        msgBox.setInformativeText( mySock->errorString() );
        msgBox.setIcon( QMessageBox::Critical );
        msgBox.exec();
    }
    else //Send a signal to the ChatTab to display the message
    {
        if( msg.getType() == Message::DecryptStandard )
            emit newMsg( msg, "blue" );
    }
}

/* The disengage function:
 * this function will disconnect the socket
 * if and only if the socket thinks it's connected */
void Chat::disengage()
{
    // If the socket is connected, disconnect it
    if( mySock->state() == QAbstractSocket::ConnectedState )
        mySock->disconnectFromHost();
}

/***********************************
 * PRIVATE SLOTS:
 ***********************************/

/* The publicKeyStep function:
 * Slot that is activated when the public key
 * is recieved */
void Chat::publicKeyStep( const string& pk )
{
    peer.second = jRsa::recoverKey( pk );
    myState |= HavePublic;
    if( amiHost )
    {
        emit sessionKeyRecv( jAes::generateKey( 32 ) );
        Message exchange = Message::SessionKeyMessage( mySessionKey, peer.second );
        writeMsg( exchange );
    }
    else
        startKeyExchange();
}

/* The sessionKeyStep Slot:
 * Slot that is activated when the session key
 * is recieved */
void Chat::sessionKeyStep( const string& key )
{
    mySessionKey = key;
    myState |= HaveSession;
}

/* The readSome slot:
 * this is connected to the socket's readyRead() signal
 * that signal is emitted when new data arrives on the
 * socket. This function takes that signal and reads
 * the data into a message */
void Chat::readSome()
{
    //Read so long as there are more bytes to read
    while( mySock->bytesAvailable() )
    {
        //The message creation can throw exceptions, catch them
        try
        {
            //Make a buffer string to store the raw data
            char buffer[Message::MAX_SIZE];
            //Get foremost message from socket
            int retval = mySock->read( buffer, Message::MAX_SIZE );
            //If there was no error
            if( retval > 0 )
            {
                //Create a message from the raw data
                Message msg( buffer, (unsigned int)retval );
                //Case statement for types of messages
                switch( msg.getType() )
                {
                    //Encrypted Standard Message
                    case Message::CryptStandard:
                    { //Braces are required for cases with declarations. Who Knew!?
                        //Create the decrypted message
                        Message decrypt( msg.aesDecrypt( mySessionKey ) );
                        //Store peer's name
                        peer.first = decrypt[0];
                        emit newMsg( decrypt, "red" );
                        break;
                    }
                    //Unencrypted Standard message
                    case Message::DecryptStandard:
                        //Store the peer's name
                        peer.first = msg[0];
                        emit newMsg( msg, "red" );
                        break;
                    //PublicKeyExchange
                    case Message::PublicKeyExchange:
                        emit publicKeyRecv( msg.getBody() );
                        break;
                    //SessionKeyExchange
                    case Message::SessionKeyExchange:
                    {
                        string sKey = Message::SessionKeyRestore( msg, me->getPrivateKey() );
                        emit sessionKeyRecv( sKey );
                        break;
                    }
                    default:
                        break;
                }
            }
        }
        catch( BaseException* e )
        {
            //Pop up message box with the errors
            QMessageBox msgBox;
            msgBox.setWindowTitle( "Critical Error" );
            msgBox.setText( "An error has occured while reading a message:" );
            msgBox.setInformativeText( QString::fromStdString( e->what() ) );
            msgBox.setIcon( QMessageBox::Critical );
            msgBox.exec();
        }
    }
}

/* The socketError slot:
 * this slot is connected to the socket's error()
 * signal. This signal is emitted when the socket
 * encounters an error. This slot deals with the error */
void Chat::socketError( QAbstractSocket::SocketError e )
{
    //If the remote host closed it's connection and the chat still thinks
    //it is connected then notify the user that his peer has disconnected
    //and tell the chat it has been disconnected.
    if( (e == QAbstractSocket::RemoteHostClosedError ||
         e == QAbstractSocket::NetworkError) && connected )
    {
        connected = false;
        disengage();
        emit disMsg( QString::fromStdString( peer.first ) );
    }
    else
    {
        //Pop up message box with the errors
        QMessageBox msgBox;
        msgBox.setWindowTitle( "Socket Error!" );
        msgBox.setText( "The socket has encountered an error" );
        msgBox.setInformativeText( mySock->errorString() );
        msgBox.setIcon( QMessageBox::Critical );
        msgBox.exec();
    }
}
